﻿//-----------------------------------------------------------------------
// <copyright file="TimedBlock.cs" company="Nathan Miller">
// Copyright (c) Nathan Miller.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </copyright>
//-----------------------------------------------------------------------
namespace TileTool.PerfSpy
{
    using System;
    using System.Diagnostics;
    using TileTool.Log;

    /// <summary>
    /// Class which times a block of code inside of a 'using' statement.
    /// 
    /// For example:
    /// 
    /// using (new TimedBlock("Hello"))
    /// {
    ///     DoStuff();
    /// }
    /// </summary>
    public class TimedBlock : IDisposable
    {
        #region Constants

        /// <summary>
        /// Log group to use.
        /// </summary>
        private const string LogGroup = "TimedBlock";

        #endregion

        #region Fields - Private

        /// <summary>
        /// Bool that tracks if we have been disposed.
        /// </summary>
        private bool disposed = false;

        /// <summary>
        /// Name of the block that we are timing.
        /// </summary>
        private string blockName = string.Empty;

        /// <summary>
        /// Stopwatch we use for timing. Timing starts on construction of
        /// this object.
        /// </summary>
        private Stopwatch stopwatch = new Stopwatch();

        #endregion

        #region Constructor

        /// <summary>
        /// Creates a new timed block with the passed name.
        /// </summary>
        /// <param name="blockName">Block name to use.</param>
        public TimedBlock(string blockName)
        {
            if (string.IsNullOrEmpty(blockName))
            {
                throw new ArgumentNullException("blockName", "Block name must be a non-empty string.");
            }

            this.blockName = blockName;
            this.stopwatch.Start();
        }

        /// <summary>
        /// Use C# destructor syntax for finalization code. 
        /// This destructor will run only if the Dispose method 
        /// does not get called. 
        /// It gives your base class the opportunity to finalize. 
        /// Do not provide destructors in types derived from this class.
        /// </summary>
        ~TimedBlock()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            this.Dispose(false);
        }

        #endregion

        #region Methods - Public

        /// <summary>
        /// Implement IDisposable. 
        /// Do not make this method virtual. 
        /// A derived class should not be able to override this method.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);

            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        #endregion

        #region Methods - Protected

        /// <summary>
        /// Dispose(bool disposing) executes in two distinct scenarios. 
        /// If disposing equals true, the method has been called directly 
        /// or indirectly by a user's code. Managed and unmanaged resources 
        /// can be disposed. 
        /// If disposing equals false, the method has been called by the 
        /// runtime from inside the finalizer and you should not reference 
        /// other objects. Only unmanaged resources can be disposed. 
        /// </summary>
        /// <param name="disposing">true if we are disposing from user code. false if we are 
        /// disposing from the runtime in a finalizer.</param>
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if (this.disposed)
            {
                return;
            }

            // If disposing equals true, dispose all managed 
            // and unmanaged resources. 
            if (disposing)
            {
                // Dispose managed resources.
                this.LogTiming();
            }

            // Call the appropriate methods to clean up 
            // unmanaged resources here. 
            // If disposing is false, 
            // only the following code is executed.
            
            // Note disposing has been done.
            this.disposed = true;
        }

        #endregion

        #region Methods - Private

        /// <summary>
        /// Log the timing for this block.
        /// </summary>
        private void LogTiming()
        {
            this.stopwatch.Stop();

            long ms = this.stopwatch.ElapsedMilliseconds;

            LogSystem.LogInfo(LogGroup, " {0} - Elapsed time - {1} ms", this.blockName, ms);
        }

        #endregion
    }
}
